package org.firewall.Server;
/**
 * 
 * Firewall Emulator
 * 
 * The assignment is to write a "stateless packet filter" and client/test
 * case generator.  Note carefully: both parts are necessary; a
 * significant part of the grade on this assignment will depend on
 * the client+test case generator.
 * 
 * The firewall piece isn't a real firewall, since we're not dedicating
 * a machine to each student.  Instead, it's a program that listens
 * on a UDP port (note: *not* TCP) and reads packets addressed to that
 * port that simulate actual packets.
 * 
 * The first thing the firewall must do, though, is read a configuration
 * file describing the policy to be enforced.  Since this is not a
 * compiler course, I will give a very simple input language.  You
 * *must* implement at least this language,
 * 
 * The input is a series of lines of the following format:
 * 
 * 	<verb> <prefix> <port> [modifier]
 * 	
 * where <verb> is either "block" or "allow" (without the quotes),
 * <prefix> is a dotted quad (e.g., 1.2.3.4) always followed by
 * a length.  Port is an integer.  Either <prefix> or <port> may also be
 * specified as "*", which means "everything".  [modifier] is an optional
 * parameter; the only legal value is "established".
 * 
 * The following are legal input lines:
 * 
 * block 127.0.0.1/32 80
 * allow 192.168.2.0/24 *
 * allow * 25
 * block * 21
 * allow * * established
 * 
 * The rules are an ordered list.
 * 
 * Input records to the UDP port contain the fields necessary for filtering:
 * 
 * sourceIP destIP sourcePort destPort <flag>
 * 
 * Exactly five fields must be present on all records.  SourceIP and
 * destIP and dotted quads; sourcePort and destPort are integers.
 * <flag> is either "start", "continue", or "end"; it says whether a packet
 * is starting a connection, part of the middle of a connection, or ending
 * one.  A rule with "established" permits only packets with "continue"
 * to match; rules without any modifier accept any packets.  Note that this
 * is a *stateless* packet filter; you do not need to track connection state.
 * 
 * So -- the firewall reads the configuration file, then listens on a
 * port for packets.  A decision must be made, according to these rules,
 * about accepting or dropping each packet.  The output of the firewall
 * is a list of packets received, their disposition, and the number of the
 * rule that caused the action.
 * 
 * The client program generates packets that are consumed by the
 * firewall.  If you wish, you may have multiple instances of the client
 * running simultaneously, each generating different packets.  Be aware
 * that you are using UDP, which means it's easy to overflow the input
 * queue for the firewall -- you'll have to rate-limit your senders.
 * You want to make sure that all rules are tested.  Of course, you also
 * need to be sure that your rule set is complex enough give the clients
 * a good workout -- you need overlapping rules, where the rule order is
 * very important.
 * 
 ************************************************************************
 * Student: Gaurav Pandey
 * School: Columbia University
 * Course: Network Security
 * Programming Homework # 3
 * File: Server.java
 ************************************************************************
 */

import java.io.*;
import java.math.*;
import java.net.*;
import java.util.*;

public class Server {
    static protected BufferedReader br = null;

	public static void main(String []args) throws IOException {
		ServerHandler sHandler = new ServerHandler();
		
		StringTokenizer str = null;
		String verb = "";
		String prefix = "";
		String port = "";
		String mod = "";
		try {
	        br = new BufferedReader(new FileReader("rules.conf"));	/*Reading the rules.conf configuration file*/
	        String s = "";
	        while((s=br.readLine())!=null) {	/*Reads every non-null line one by one*/
	        	verb = "";
	        	prefix = "";
	        	port = "";
	        	mod = "";
	        	if(!s.isEmpty()) {    		/*line should not be null*/
	        		str = new StringTokenizer(s);	
	        		if(str.countTokens()!=4 && str.countTokens()!=3)
	        			continue;
	        		verb = str.nextToken();
	        		if(!verb.equals("allow") && !verb.equals("block"))
	        			continue;
	        		prefix = str.nextToken();
	        		port = str.nextToken();
	        		int tempPort = 0;
	        		try {
	        			tempPort=Integer.parseInt(port);
	        			if((tempPort<0 && tempPort>=65536))
	        				continue;
	        		}
	        		catch (NumberFormatException nfe ) {
	        			if(!port.equals("*"))
	        				continue;
	        		}
	        		if(str.hasMoreTokens())
	        			mod = str.nextToken();
	        		/**Parsing the contents of the configuration file and only allowing the entries
	        		 * which do match the format 'sourceIP destIP sourcePort destPort <flag>' to be used as
	        		 * rules in the packet filter firewall
	        		 * */
	        		try {
	        		if((verb.equals("block") || verb.equals("allow")) && (mod.equals("") || mod.equals("established"))) {
			        	if(prefix.equals("*")) {
			        		if(port.equals("*")) {
			        			ServerHandler.arrRules.add(s);
			        		}
			        		else if(!port.equals("*") && Integer.parseInt(port)>=0 && Integer.parseInt(port)<65536)
			        			ServerHandler.arrRules.add(s);
			        	}
			        	else if(!prefix.equals("*") && validcidr(prefix)) {
			        		if(port.equals("*")) {
			        			ServerHandler.arrRules.add(s);
			        		}
			        		else if(!port.equals("*") && Integer.parseInt(port)>=0 && Integer.parseInt(port)<65536)
			        			ServerHandler.arrRules.add(s);
			        	}
	        		}
	        		}
	        		catch(NumberFormatException nfe) {}
	        	}
	        }
	        int i=0;
	        System.out.println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
	        System.out.println("Parsed Configuration Rules loaded in Data Structure are:");
	        System.out.println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
	        while(i<ServerHandler.arrRules.size()) {
	        	System.out.println((i+1)+" "+ServerHandler.arrRules.get(i));
	        	i++;
	        }
	        System.out.println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
	        System.out.println("Listening for UDP packets on port 4445:");
	    }
		catch (FileNotFoundException e) {
	        System.err.println("Could not open file.");
	    }
		catch(NumberFormatException nfe){}
		sHandler.start();
	}
	
	public static boolean validcidr(String cidr) {	/*validating the cidr mentioned in the configuration file*/
		try {
		StringTokenizer st = new StringTokenizer( cidr, "." ) ;
		int st1 = Integer.parseInt(st.nextToken());
		int st2 = Integer.parseInt(st.nextToken());
		int st3 = Integer.parseInt(st.nextToken());
		String st4 = st.nextToken();
		StringTokenizer stt = new StringTokenizer(st4,"/");
		if(stt.countTokens()!=2)
			return false;
		int st4a = Integer.parseInt(stt.nextToken());
		int st4b = Integer.parseInt(stt.nextToken());
		if(st1>=0 && st1<256 && st2>=0 && st2<256 && st3>=0 && st3<256 && st4a>=0 && st4a<256 && st4b>=0 && st4b<33)
			return true;
		else 
			return false;
		}
		catch(NumberFormatException nfe) {
			return false;
		}
	}
}

class ServerHandler extends Thread {
    protected DatagramSocket socket = null;
    protected BufferedReader in = null;
	private static String  SLASH = "/";
    private static String  DOT = ".";
    /**
     * ArrayList is used as an efficient data structure for maintaing the configuration file
     * */
    public static ArrayList<String> arrRules = new  ArrayList<String>();	

    public ServerHandler() throws IOException {
    	this("ServerHandler");
    }

    public ServerHandler(String name) throws IOException {
        super(name);
        socket = new DatagramSocket(4446); /*Server is listening on port 4445*/
    }

    public void run() {
    	StringTokenizer str = null, arrstr = null;
    	String action = "", destCIDR = "", rulesPort = "",modifier = "";
    	
    	String cliPacketData = "",tempCliPacketData = "", sourceIP = "", destIP = "", flag = "";
    	int sourcePort = 0, destPort = 0, i=0, arrSize=0;
    	
    	boolean matcher = false, check1 = false, sourceIPMatcher = false, destIPMatcher = false, srcPortFLAG = false, dstPortFLAG = false;
    	byte[] buf;
    	
        while (true) {
            try {
                buf = new byte[256];
                sourceIPMatcher = false;
                destIPMatcher = false;
                srcPortFLAG = false;
                dstPortFLAG = false;
                
                // receive request
                DatagramPacket packet = new DatagramPacket(buf, buf.length);	/*packet for receiving incoming packets from client*/
                socket.receive(packet); 	/*Receiving the packets over the network*/

                cliPacketData = new String(packet.getData());
                
                str = new StringTokenizer(cliPacketData);
                if(str.countTokens()!=5) {
                	System.out.println("CLIENT: "+packet.getAddress().toString()+" ## PACKET: "+cliPacketData+"  ####  ACTION: BLOCK  ##  RULE_NUMBER: MALFORMED_PACKET");
                	continue;
                }
                //sourceIP destIP sourcePort destPort <flag>
                sourceIP = str.nextToken();
                sourceIPMatcher = ValidateIPAddress(sourceIP.trim());	/*validating whether the ip address is in in proper format*/
                if(!sourceIPMatcher) {
                	System.out.println("CLIENT: "+packet.getAddress().toString()+" ## PACKET: "+cliPacketData+"  ####  ACTION: BLOCK  ##  RULE_NUMBER: INCORRECT_SOURCE_IP");
                	continue;
                }
                
                destIP = str.nextToken();
                destIPMatcher = ValidateIPAddress(destIP.trim());	/*validating whether the ip address is in in proper format*/
                if(!destIPMatcher) {
                	System.out.println("CLIENT: "+packet.getAddress().toString()+" ## PACKET: "+cliPacketData+"  ####  ACTION: BLOCK  ##  RULE_NUMBER: INCORRECT_DEST_IP ");
                	continue;
                }
                
                String srcstr = "",dststr = "";
                try {
                	srcstr = str.nextToken();
                	sourcePort = Integer.parseInt(srcstr);
                	if(sourcePort<0 || sourcePort>65535) {
                		System.out.println("CLIENT: "+packet.getAddress().toString()+" ## PACKET: "+cliPacketData+"  ####  ACTION: BLOCK  ##  RULE_NUMBER: INCORRECT_SOURCE_PORT ");
                    	continue;
                	}
                }
                catch (NumberFormatException nfe) {
                	srcPortFLAG = true;
                }
                if(srcPortFLAG) {
                	System.out.println("CLIENT: "+packet.getAddress().toString()+" ## PACKET: "+cliPacketData+"  ####  ACTION: BLOCK  ##  RULE_NUMBER: INCORRECT_SOURCE_PORT ");
                	continue;
                }
                
                try {
                	dststr = str.nextToken();
                	destPort = Integer.parseInt(dststr);
                	if(destPort<0 || destPort>65535) {
                		System.out.println("CLIENT: "+packet.getAddress().toString()+" ## PACKET: "+cliPacketData+"  ####  ACTION: BLOCK  ##  RULE_NUMBER: INCORRECT_DEST_PORT ");
                    	continue;
                	}
                }
                catch (NumberFormatException nfe) {
                	dstPortFLAG = true;
                }
                if(dstPortFLAG) {
                	System.out.println("CLIENT: "+packet.getAddress().toString()+" ## PACKET: "+cliPacketData+"  ####  ACTION: BLOCK  ##  RULE_NUMBER: INCORRECT_DEST_PORT ");
                	continue;
                }
                flag = str.nextToken().trim();
                if(flag.equals("start") || flag.equals("continue") || flag.equals("end")) {}
                else {
                	System.out.println("CLIENT: "+packet.getAddress().toString()+" ## PACKET: "+cliPacketData+"  ####  ACTION: BLOCK  ##  RULE_NUMBER: INCORRECT_FLAG_SPECIFIED ");
                	continue;
                }
                /**
                 * Decision making functionality to check whther the Source IP, Destination IP,
                 * Source Port and Destination Port are in the proper format 
                 * */
                tempCliPacketData = sourceIP+"  "+destIP+"  "+srcstr+"  "+dststr+"  "+flag;


                
                arrSize = arrRules.size();
                /**
                 * Application Logic matching the incoming packets with the rules imposed stored
                 * in the ArrayList data dtructure
                 * */
                while(i<arrSize && matcher == false) {
                	arrstr = new StringTokenizer(arrRules.get(i));
                	action = arrstr.nextToken();
                	destCIDR = arrstr.nextToken();
                	rulesPort = arrstr.nextToken();
                	modifier = "";
                	if(arrstr.hasMoreTokens()) {
                		modifier = arrstr.nextToken();
                	}

                	if(destCIDR.equals("*")) {
                		if(rulesPort.equals("*")) {
            				matcher = true;
            			}
            			else if(Integer.parseInt(rulesPort)==destPort) {
            				matcher = true;
            			}
                	}
                	else {
                		check1 = addressInSubnet(destIP, destCIDR);
                		if(check1 == true) {
                			if(rulesPort.equals("*")) {
                				matcher = true;
                			}
                			else if(Integer.parseInt(rulesPort)==destPort) {
                				matcher = true;
                			}
                		}
                	}
                	i++;
                }
                
                /**
                 * Displaying the information on the console depending on the type of decision reached
                 * */
                if(modifier.equals("established")) {
                	if(flag.contains("continue")) {
                		System.out.println("CLIENT: "+packet.getAddress().toString()+" ## PACKET: "+tempCliPacketData+"  ####  ACTION: "+action.toUpperCase()+"  ##  RULE_NUMBER: "+i);
                	}
                	else {
                		System.out.println("CLIENT: "+packet.getAddress().toString()+" ## PACKET: "+tempCliPacketData+"  ####  ACTION: BLOCK"+"  ##  RULE_NUMBER: "+i);
                	}
                	
                }
                else{
                	if(matcher == true) {
                    	System.out.println("CLIENT: "+packet.getAddress().toString()+" ## PACKET: "+tempCliPacketData+"  ####  ACTION: "+action.toUpperCase()+"  ##  RULE_NUMBER: "+i);
                    }
                    else {
                    	System.out.println("CLIENT: "+packet.getAddress().toString()+" ## PACKET: "+tempCliPacketData+"  #### ACTION: BLOCK  ##  RULE_NUMBER: NO_RULE_MATCH ");
                    }
                }
                
                i=0;
                check1 = false;
                matcher = false;

            }
            catch (IOException e) {
                e.printStackTrace();
            }
        }
        //socket.close();
    }
    
    /*Validating the IP Address*/
    public static boolean ValidateIPAddress( String  ipAddress ) {
    	String[] parts = ipAddress.split( "\\." );
        if ( parts.length != 4 ) {
            return false;
        }
        for ( String s : parts ) {
        	try {
            int i = Integer.parseInt( s );
            if ( (i < 0) || (i > 255) ) {
                return false;
            }
        	}
        	catch (NumberFormatException nfe) {
        		return false;
        	}
        }
        return true;
    }
    
    /*Checker to match whether an IP Address lies in the CIDR rule specified in the configuration file*/
    public static boolean addressInSubnet ( String pIPAddr, String pCIDR) {
        BigInteger theMask = new BigInteger ( mkBinaryMask(pCIDR),2 );
        BigInteger testIPAddress = new BigInteger(mkBinaryAddress(pIPAddr).substring(0,mkBinaryMask(pCIDR).length()), 2 );
        BigInteger theCIDR = new BigInteger (mkBinaryAddress(pCIDR.substring(0,pCIDR.indexOf(SLASH))).substring(0,mkBinaryMask(pCIDR).length()), 2 );
        return (testIPAddress.and(theMask).equals(theCIDR));
    }

    private static String mkBinaryTestMask ( String pCIDR ) {
    	String rVal = new String("");
    	//  Special case for 0 - needs to be ZERO
    	if (Integer.parseInt(pCIDR.substring( pCIDR.indexOf(SLASH) + 1 )) > 0 ) {
    		for ( int x=0; x < 32 - Integer.parseInt(pCIDR.substring(pCIDR.indexOf(SLASH) + 1 )); x++) {
    			rVal = rVal.concat("1");
    		}
    	}
    	return ((rVal.length() == 0) ? ("0") : (rVal) );
    }

    private static String mkBinaryMask ( String pCIDR ) {
    	String rVal = new String("");
    	for ( int x=0; x < Integer.parseInt(pCIDR.substring( pCIDR.indexOf(SLASH) + 1)); x++) {
    		rVal = rVal.concat("1");
    	}
    	return ((rVal.length() == 0) ? ("0") : (rVal) );
    }

    private static String mkBinaryAddress ( String pIPAdr ) {
    	String rVal = new String("");
    	StringTokenizer st = new StringTokenizer(pIPAdr,".");
    	while (st.hasMoreTokens()) {
    		int tInt = Integer.parseInt(st.nextToken());
    		rVal = rVal.concat (
    				("00000000").concat(Integer.toBinaryString(tInt)).substring(
    						("00000000").concat(Integer.toBinaryString(tInt)).length()  - 8 )
    		);
    	}
    	return ( rVal );
    }

    private String mkBinaryAddress ( byte[] bRay) {
    	String rVal = new String("");
    	for ( int x = 0; x < bRay.length; x++ ) {
    		rVal = rVal.concat (
    				("00000000").concat(Integer.toBinaryString( ((int)bRay[x]) &255)).substring(
    						("00000000").concat(Integer.toBinaryString( ((int)bRay[x]) & 
    								255)).length() - 8 )
    		);
    	}
    	return (rVal);
    }
}
